#include "longinstance2b.h"
#include "freefuncs.h"
#include <string>
#include <iostream>
#include <math.h>
#include <cassert>
using namespace std;



void LongInstance2B::addPhase(const Neighborhood2& newNB, int newLabel)
{
	this->neighborhood2.push_back(newNB);
	this->updateTotalHash();
	this->label = newLabel;
}

void LongInstance2B::addPhase(const ImageMatrix& image, int row, int col, int nbRadius, int newLabel)
{
	Neighborhood2 toBeAdded(image, row, col, nbRadius);
	this->neighborhood2.push_back(toBeAdded);
	this->label = newLabel;
	this->updateTotalHash();

}

int LongInstance2B::getFeatureSize() const
{
	if (this->neighborhood2.size() == 0)
		return 0;
	//Neighborhood2* nb0 = (Neighborhood2*) this->neighborhood2[0];
	return (int)this->neighborhood2.size() * this->neighborhood2[0].getSize();
	//return (int)this->neighborhood.size() * intPosPow((2*NRADIUS+1),2);
}

LongInstance2B::LongInstance2B(const LongInstance2B& other)
{
	this->label = other.label;
	this->totalHash = other.totalHash;
	this->conflictNum = other.conflictNum;
	for (int i = 0; i != (int)other.coordinate.size(); i++)
		this->coordinate.push_back(other.coordinate[i]);
	//Neighborhood2 *newNB;
	
	for (vector<Neighborhood2>::const_iterator i = other.neighborhood2.begin(); i != other.neighborhood2.end(); ++i)
	{
		this->neighborhood2.push_back(*i);
	}
}

LongInstance2B::LongInstance2B ()
{
	this->label = -1;
	this->totalHash = -1;
	this->conflictNum = 0;
}

bool LongInstance2B::hasSameFeature (const LongInstance2B& other) const
{
	if (this->neighborhood2.size() != other.neighborhood2.size())
		return false;
	//Neighborhood2 *myNBP, *otherNBP;
	for (int i = 0; i < (int) this->neighborhood2.size(); ++i)
	{
		//myNBP =  this->neighborhood2[i];
		//otherNBP = other.neighborhood2[i];
		if (!neighborhood2[i].isEqual(other.neighborhood2[i]))
			return false;
	}
	return true;
}

bool LongInstance2B::hasSameFeature (const vector<Neighborhood2>& nb) const
{
	if (this->neighborhood2.size() != nb.size())
		return false;
	//Neighborhood2 *myNBP, *otherNBP;
	for (int i = 0; i < (int) this->neighborhood2.size(); ++i)
	{
		//myNBP = (Neighborhood2*) this->neighborhood2[i];
		//otherNBP = (Neighborhood2*) nb[i];
		if (!neighborhood2[i].isEqual(nb[i]))
			return false;
	}
	return true;
}


void LongInstance2B::addOthersCoordinates(const LongInstance2B& other)
{
	for (int i = 0; i < (int) other.coordinate.size(); i++)
		this->addCoordinate(other.coordinate[i]);
}



int LongInstance2B::getFeature (int longFeatureIndex) const
{
	int numberOfNBsInOneNeighborhood = intPosPow((2*this->getNBRadius()+1),2);
	assert(longFeatureIndex < (int) this->neighborhood2.size() * numberOfNBsInOneNeighborhood &&
		"assertion failed: trying to read an out of range feature");
	
	int neighborhoodIndex = longFeatureIndex / numberOfNBsInOneNeighborhood;
	int featureIndex = longFeatureIndex % numberOfNBsInOneNeighborhood;
	return this->neighborhood2[neighborhoodIndex].getFeature(featureIndex);
}

int LongInstance2B::getFeature (int neighborhoodIndex, int featureIndex) const
{
	return this->neighborhood2[neighborhoodIndex].getFeature(featureIndex);
}

int LongInstance2B::getOccuranceNum(void) const
{
	return this->coordinate.size();
}

unsigned long LongInstance2B::getTotalHash () const
{
	return this->totalHash;
}

void LongInstance2B::setFeature (int neighborhoodIndex, int featureIndex, int value)
{
	int numberOfNBsInOneNeighborhood = intPosPow((2*this->getNBRadius()+1),2);
	assert(neighborhoodIndex < (int) this->neighborhood2.size() && featureIndex < numberOfNBsInOneNeighborhood
		&& value < CELLSTATES);
	this->neighborhood2[neighborhoodIndex].setFeature(featureIndex, value); //updates the internal hash too
	this->updateTotalHash();
}

void LongInstance2B::setFeature (int longFeatureIndex, int value)
{
	int numberOfNBsInOneNeighborhood = intPosPow((2*this->getNBRadius()+1),2);
	assert(longFeatureIndex < (int) this->neighborhood2.size() * numberOfNBsInOneNeighborhood &&
		value < CELLSTATES);
	int neighborhoodIndex = longFeatureIndex / numberOfNBsInOneNeighborhood;
	int featureIndex = longFeatureIndex % numberOfNBsInOneNeighborhood;
	this->neighborhood2[neighborhoodIndex].setFeature(featureIndex, value);
	this->updateTotalHash();
}

int LongInstance2B::getNBRadius () const
{
	if (this->neighborhood2.size() == 0)
		return -1;
	else
		return this->neighborhood2.begin()->getRadius();
}

void LongInstance2B::setLabel (int value)
{
	assert (label < CELLSTATES);
	this->label = value;
}

LongInstance2B & LongInstance2B::operator= (const LongInstance2B& other)
{
	if (&other == this)
		return *this;
	vector<Neighborhood2>::const_iterator i = other.neighborhood2.begin();
	this->neighborhood2.clear();
	for (; i != other.neighborhood2.end(); i++)
	{
		//Neighborhood2* localNB = new Neighborhood2(*other.neighborhood2[i]);
		this->neighborhood2.push_back(*i);
	}
	this->coordinate.clear();
	for (int i = 0; i != (int) other.coordinate.size(); i++)
		this->coordinate.push_back(other.coordinate[i]);
	this->totalHash = other.totalHash;
	this->conflictNum = other.conflictNum;
	this->label = other.label;
	return * this;
}

//bool LongInstance2B::hasSamePartialHash(LongInstance2B& other) const
//{
//	if (this->neighborhood2.size() != other.neighborhood2.size())
//		return false;
//	for (int i = 0; i != (int) this->neighborhood2.size(); i++)
//		if (this->neighborhood2[i].getHash() != other.neighborhood2[i].getHash()) //TODO: This hash thing is not a good idea. Let's remove it.
//			return false;
//	return true;
//}
	
void LongInstance2B::print(void) const
{
	int numberOfNBsInOneNeighborhood = intPosPow((2*this->getNBRadius()+1),2);
	for (int i = 0; i != (int) this->neighborhood2.size(); i++)
	{
		for (int j = 0; j != numberOfNBsInOneNeighborhood; j++)
			cout << this->neighborhood2[i].getFeature(j) << " ";
		//cout << ", ";
	}
	cout << ": " << this->label << endl;
}

void LongInstance2B::printDetail(void) const
{
	int numberOfNBsInOneNeighborhood = intPosPow((2*this->getNBRadius()+1),2);
	cout << "LongInstance2B features: " << endl;
	for (int i = 0; i != (int)this->neighborhood2.size(); i++)
	{
		for (int j = 0; j != numberOfNBsInOneNeighborhood; j++)
			cout << this->neighborhood2[i].getFeature(j) << " ";
		cout << ", ";
	}
	cout << "- " << this->label << endl;
	cout << "LongInstance2B hashes: " << endl;
	for (int i = 0; i != (int)this->neighborhood2.size(); i++)
		cout << this->neighborhood2[i].getHash() << ", ";
	cout << endl;
	cout << "Total hash: " << this->totalHash << endl;
	cout << "Number of coordinates where this instance occurs: " << this->coordinate.size() << endl;
	cout << "Coordinate where this instance occurs: " << endl;
	for (int i = 0; i != (int)this->coordinate.size(); i++)
	{
		this->coordinate[i].print();
		cout << " ";
	}
	cout << endl;
}

bool LongInstance2B::isNextPhase (LongInstance2B& prevPhase) const
{
	if (this->neighborhood2.size() != prevPhase.neighborhood2.size() + 1)
		return false;
	for (int i = 0; i != (int)prevPhase.neighborhood2.size(); i++)
		if (!this->neighborhood2[i].hasSameFeature(prevPhase.neighborhood2[i]))
			return false;
	return true;
}

bool LongInstance2B::isPrevPhase (LongInstance2B& nextPhase) const
{
	if (this->neighborhood2.size() != nextPhase.neighborhood2.size() - 1)
		return false;
	for (int i = 0; i != (int) this->neighborhood2.size(); i++)
		if (!this->neighborhood2[i].hasSameFeature(nextPhase.neighborhood2[i]))
			return false;
	return true;
}

void LongInstance2B::updateTotalHash(void)
{
	int numberOfNBsInOneNeighborhood = intPosPow((2*this->getNBRadius()+1),2);
	int bitOrder = 0;
	for (int i = 0; i != (int)this->neighborhood2.size(); i++)
		for (int j = 0; j != numberOfNBsInOneNeighborhood; j++)
		{
			this->totalHash += this->neighborhood2[i].getFeature(j) +  bitOrder++;
			//bitOrder++;
		}
}

//void LongInstance2B::updateFromPartialHashArray(unsigned long* hashValue, int hashValueSize, int label)
//{
//	vector<Neighborhood2*>::const_iterator i = this->neighborhood2.begin();
//	for (; i != neighborhood2.end(); ++i)
//		delete *i;
//	this->neighborhood2.clear();
//	for (int i = 0; i != hashValueSize; i++)
//	{
//		Neighborhood2* toBeAdded = new Neighborhood2(hashValue[i]);
//		this->neighborhood2.push_back(toBeAdded);
//	}
//	this->label = label;
//	this->updateTotalHash();
//}

void LongInstance2B::clear()
{
	this->neighborhood2.clear();
	this->coordinate.clear();
	this->label = -1;
	this->totalHash = -1;
	this->conflictNum = 0;
}
	
void LongInstance2B::getCooBorders(int& top, int& bottom, int& left, int& right) const
{
	int cooSize = this->getOccuranceNum();
	assert (cooSize > 0);

	int t = this->getRowOfCoordinate(0);
	int b = t;
	int l = this->getColOfCoordinate(0);
	int r = l;
	
	if (cooSize > 1)
		for (int i = 1; i < cooSize; i++)
		{
			if (t > this->getRowOfCoordinate(i))
				t = this->getRowOfCoordinate(i);
			else if (b < this->getRowOfCoordinate(i))
				b = this->getRowOfCoordinate(i);
			if (l > this->getColOfCoordinate(i))
				l = this->getColOfCoordinate(i);
			else if ( r < this->getColOfCoordinate(i))
				r = this->getColOfCoordinate(i);
		}

		top = t;
		bottom = b;
		left = l;
		right = r;
}


//LongInstance2B& LongInstance2B::removeFeature_emptyCoo(int featureIndex) const
//{
//	if (featureIndex > this->getFeatureSize())
//	{
//		cout << "Error: Requested to remove a neighbor that does not exist." << endl;
//		assert(true);
//	}
//	LongInstance2B* result = new LongInstance2B();
//	for (int nbIndex = 0; nbIndex < this->neighborhoodNum(); nbIndex++)
//		Neighborhood2 toBeAdded(
//	for (int i = 0; i < featureIndex; ++i)
//		result->neighborhood2.push_back(this->getFeature(i))//here we are adding to neighborhood2, but we have to somehow add to neighborhood2[0]. might need to add low level functions.
//	for (int i = neighborIndex+1; i < this->getFeatureSize(); ++i)
//		result->neighborhood2.push_back(this->getFeature(i));
//	result->conflictNum = conflictNum;
//	result->label = label;
//	result->totalHash = totalHash;
//
//	return *result;
//}

//LongInstance2B::LongInstance2B (string feature,  int label)
//{
//	assert(feature.size() % intPosPow((2*NRADIUS+1),2) == 0);
//	int a = 0;
//	int neighborhoodNum = feature.size() / intPosPow((2*NRADIUS+1),2);
//	Neighborhood2* blankNB;
//	for (int i = 0; i != neighborhoodNum; i++)
//	{
//		blankNB = new Neighborhood2;
//		this->neighborhood2.push_back(blankNB);
//		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
//			this->neighborhood2[i]->setFeatureLeaveHash(j, feature.at(a++) - 48); //'0'=48, '1'=49, etc.
//		this->neighborhood2[i]->updateHash();
//	}
//	this->label = label;
//	this->conflictNum = 0;
//	this->updateTotalHash();
//}
//	
//	
//LongInstance2B::LongInstance2B (int* feature, int featureSize, int label)
//{
//	assert(featureSize % intPosPow((2*NRADIUS+1),2) == 0);
//	int a = 0;
//	int neighborhoodNum = featureSize / intPosPow((2*NRADIUS+1),2);
//	Neighborhood2* blankNB;
//	for (int i = 0; i != neighborhoodNum; i++)
//	{
//		blankNB = new Neighborhood2;
//		this->neighborhood2.push_back(blankNB);
//		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
//			this->neighborhood2[i]->setFeatureLeaveHash(j, feature[a++]);
//		this->neighborhood2[i]->updateHash();
//	}
//	this->label = label;
//	this->conflictNum = 0;
//	this->updateTotalHash();
//}
//	
//LongInstance2B::LongInstance2B (string feature, int label, int row, int col)
//{
//	assert(feature.size() % intPosPow((2*NRADIUS+1),2) == 0);
//	int a = 0;
//	int neighborhoodNum = feature.size() / intPosPow((2*NRADIUS+1),2);
//	Neighborhood2* blankNB;
//	for (int i = 0; i != neighborhoodNum; i++)
//	{
//		blankNB = new Neighborhood2;
//		this->neighborhood2.push_back(blankNB);
//		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
//			this->neighborhood2[i]->setFeatureLeaveHash(j, feature.at(a++) - 48); //'0'=48, '1'=49, etc.
//		this->neighborhood2[i]->updateHash();
//	}
//	this->coordinate.push_back(Coordinate(row, col));
//	this->label = label;
//	this->conflictNum = 0;
//	this->updateTotalHash();
//}
	
	
//LongInstance2B::LongInstance2B (int* feature, int featureSize, int label, int row, int col)
//{
//	//assert(featureSize % intPosPow((2*this->+1),2) == 0);
//	assert ((int)sqrt(featureSize) ==sqrt(featureSize) && "featureSize should be a complete square number");
//	int a = 0;
//	int nbRadius = (int) sqrt((double)featureSize);
//	int neighborhoodNum = featureSize / intPosPow((2*NRADIUS+1),2);
//	//Neighborhood2* blankNB;
//	for (int i = 0; i != neighborhoodNum; i++)
//	{
//		//blankNB = new Neighborhood2;
//		this->neighborhood2.push_back(blankNB);
//		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
//			this->neighborhood2[i]->setFeatureLeaveHash(j, feature[a++]);
//		this->neighborhood2[i]->updateHash();
//	}
//	this->coordinate.push_back(Coordinate(row, col));
//	this->label = label;
//	this->conflictNum = 0;
//	this->updateTotalHash();
//}

//Warning: LongInstance2B has no idea about EightLayerIM
//LongInstance2B::LongInstance2B (const EightLayerIM& images, int row, int col, int size): conflictNum(0)
//{
//	this->coordinate.push_back(Coordinate(row, col));
//	for (int i = 0; i < images.getValidImages()-1; i++)
//		this->neighborhood2.push_back(Neighborhood2(images.getImage(i), row, col));
//	this->label = images.getImage(images.getValidImages()-1).getValue(row, col);
//}


LongInstance2B::~LongInstance2B()
{
	this->neighborhood2.clear();
	this->coordinate.clear();
	this->label = 0;
	this->totalHash = 0;
	this->conflictNum = 0;

}

int LongInstance2B::getNetInfoSizeInBits(void) const
{
	/*
	  The effective information in an object:
	  one if-then rule and n rectangles (minor rectangles).
	  the rule size is "getFeatureSize()" number of bits for the if part and one bit for the "then" part.
	  the rectangles need n * 2 (2 coordinate is stored per rectangle) * 8 (number of bits per coo) bits.
	  */
	//throw(exception("not implemented"));
	return ((this->getFeatureSize() + 1) * BITS_PER_NBR + this->coordinate.size() *2*BITS_PER_ROWCOL);
}

bool LongInstance2B::operator== (const LongInstance2B& other) const
{
	if (this->hasSameFeature(other) && this->label == other.label)
		return true;
	else
		return false;
}

bool LongInstance2B::conflicts (const LongInstance2B& other) const
{
	if (this->hasSameFeature(other) && this->label != other.label)
		return true;
	else
		return false;
}

int LongInstance2B::getNeighborhood2Size(void) const
{
	if (this->neighborhood2.size() == 0)
		return -1;
	else
		return this->neighborhood2[0].getSize();
}

LongInstance2B::LongInstance2B (string feature, int label)//Fix it and also write the similar constructor for FreeFormRule + FreeFormRuleBase
{
	assert(feature.size() % intPosPow((2*NRADIUS+1),2) == 0);
	int a = 0;
	int neighborhoodNum = feature.size() / intPosPow((2*NRADIUS+1),2);
	for (int i = 0; i != neighborhoodNum; i++)
	{
		Neighborhood2 toBeAdded;
		for (int j = 0; j != intPosPow((2*NRADIUS+1),2); j++)
		{			
			toBeAdded.setFeatureLeaveHash(j, feature.at(a++) - 48); //'0'=48, '1'=49, etc.
		}
		toBeAdded.updateHash();
		this->neighborhood2.push_back(toBeAdded);
	}
	this->label = label;
	this->conflictNum = 0;
	this->updateTotalHash();
}

LongInstance2B::LongInstance2B (string* feature, int strNum, int label)
{
	for (int i = 0; i < strNum; ++i)
		this->neighborhood2.push_back(Neighborhood2(feature[i]));
	this->label = label;
}

//ostream& operator<< (ostream& stream, const LongInstance2B& li2b)
//{
//	for (vector<Neighborhood2>::const_iterator i = li2b.neighborhood2.begin(); i != li2b.neighborhood2.end(); ++i)
//		stream << "(" << *i << ") ";
//	stream << " -> " << li2b.label;
//	return stream;
//}

ostream& operator<< (ostream& stream, const LongInstance2B& li2b)
{
	for (vector<Neighborhood2>::const_iterator i = li2b.neighborhood2.begin(); i != li2b.neighborhood2.end(); ++i)
		stream << "(" << *i << ") ";
	stream << " -> " << li2b.label << endl;
	if (li2b.coordinate.size() > 0)
	{
		stream << "Unless: " << endl;
		for (vector<Coordinate>::const_iterator i = li2b.coordinate.begin(); i != li2b.coordinate.end(); ++i)
			stream << *i << endl;
	}
		return stream;
}

int LongInstance2B::getNBNum() const
{
	return (int) neighborhood2.size();
}

bool li2bLabelIsZero (const LongInstance2B& li2b)
{
	return (li2b.label == 0);
}